package org.hsc.proxy;
/*
 * HttpResponse.java
 *
 * Created on December 22, 2007
 *
 * Name:        Moty Michaely   |   Alon Halfon
 * ID:          043413087       |   300545969
 */
import java.util.LinkedList;
import java.io.*;
import java.text.DateFormat;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.*;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * An http response.
 * 
 * @author Alon Halfon
 */
public class HttpResponse {
	private final static Logger logger=LoggerFactory.getLogger(HttpResponse.class);
    public static final String FORBIDDEN = 
            "HTTP/1.1 403 Forbidden\r\n" +
            "Content-Type: text/html;\r\n" +
            "Content-Length: 164\r\n" +
            "\r\n" +
            "<html><head>" +
            "<title>403 Forbidden</title>"+
            "</head><body>"+
            "<h1>403 Forbidden</h1>"+
            "<p>The request exceeds proxy policy.</p>"+
            "<hr>"+
            "<address>Proxy Server</address>"+
            "</body></html>";
    
    /** each key is header, the value is linked list of values */    
    private HashMap<String,LinkedList<String>> headers;
    
    /** the first line of the response */  
    private String statusLine;
    
    /** Holds the response status code */
    private int statusCode;
    
    /** Holds the response status reason phrase */
    private String statusReasonPhrase;
    
    /** holds the http version */
    private String version;
    
    /** Holds the response body input stream */  
    private ByteArrayOutputStream responseBody;
    
    /**
     * create empty HttpResponse (later will be filled with parse function)
     */  
    public HttpResponse(){
    }
    /**
     * create HttpResponse and parsing the io stream by parse function
     */         
    public HttpResponse (InputStream io) throws IOException{
        this.parse(io); 
    }
    /**
     * return the status line (first response's line)
     */  
    public String getStatusLine(){
       return statusLine; 
       //the line as it.
    }
    
    /**
     * return true if connection is persistent
     */
    public boolean getKeepAlive (){
        LinkedList<String> values = null;
        String key = null;
        // Default is true for http 1.1
        boolean keepAlive = version == null ? false : version.equalsIgnoreCase("HTTP/1.1");
        /* 
         * HTTP/1.1 defines the "close" connection option for the sender 
         * to signal that the connection will be closed after completion of the response
         */ 
        key = "Connection";
        if( headers.containsKey(key) ){
            if( (values = headers.get(key)) != null ){
                if( values.contains("close") )
                    return false;
            }
        }
        key = "Proxy-Connection";
        if( headers.containsKey(key) ){
            if( (values = headers.get(key)) != null ){
                if( values.contains("close") )
                    return false;
            }
        }
        return keepAlive;
    }
    
     
    private String headersString = null;
    
    
    /**
     * parse the response reading from the io
     */ 
    public void parse(InputStream in) throws IOException {
    	
        synchronized( in ) {
            try {
                while( in.available() == 0 )
                    Thread.sleep(10);
            } catch (InterruptedException ie) {}

            StringBuffer h = new StringBuffer(1024);
            int b;
            boolean stop = false;
            int len=0;
            while( !stop && (b=in.read()) > -1 ) {
                h.append((char)b);
                len=h.length();
                stop =  (h.charAt(len-1) == '\n' &&
                        h.charAt(len-2) == '\r' &&
                        h.charAt(len-3) == '\n' &&
                        h.charAt(len-4) == '\r');
            }
            //String headersString = h.toString();
            headersString = h.toString();

            headers=new LinkedHashMap<String,LinkedList<String>>();

            String line=null;
            String[] lines = headersString.split("\r\n");
            String[] splitedLine, header;
            String key, value;

            for( int i=0; i<lines.length; ++i ){
                line=lines[i];
                if( i==0 ) {// Status Line
                        this.statusLine = line;
                        
                        logger.debug("get statusLine:"+line);
                        splitedLine = this.statusLine.split(" ", 3);
                        //the status line is the first line
                        version = splitedLine[0];
                        statusCode = Integer.parseInt(splitedLine[1]);
                        statusReasonPhrase = splitedLine[2];
                }  else {
                    // Message-header = field-name ":" [ field-value ]			
                    header = line.split(":", 2);
                    key = header[0].trim();
                    value = header[1].trim();
                    //
                    if (!headers.containsKey(key)){
                        LinkedList<String> values=new LinkedList<String>();
                        values.add(value);
                        headers.put(key, values); 
                    } else{
                        LinkedList<String> values= headers.get(key);
                        values.add(value);
                    }
                }
            }

            // We are done with the headers, start storing the request body, char by char.
            int contentLength = this.getContentLength();
            this.responseBody = new ByteArrayOutputStream(contentLength);
            byte [] buffer = new byte[8096];
            int bytesToRead = contentLength, bytesRead = 0;
            while( contentLength > 0 ){
                bytesToRead=Math.min(contentLength,buffer.length);
                if( (bytesRead = in.read(buffer, 0, bytesToRead)) > -1 ){
                    this.responseBody.write(buffer,0,bytesRead);
                    contentLength-=bytesRead;
                }
                else
                    contentLength = 0;
            }
            this.responseBody.flush();
        }
    }
    
    /*
     * return the status code of the response
     */         
    public int getStatusCode(){
        return statusCode;
    }
    /*
     * return the status reason phrase from the status line
     */ 
    public String getStatusReasonPhrase(){
        return statusReasonPhrase;
    }
    /*
     * return the io that "contain" the response body
     */ 
    public InputStream getResponseBody (){
        return new ByteArrayInputStream(this.responseBody.toByteArray());
    }

    /*
     * return the headers HashMap as unmodifiable map
     */ 
    public Map<String,LinkedList<String>> getHeaders() {
        return Collections.unmodifiableMap(headers);
    }
    
    /*
     * return the last modified header value, or DateTime.Now() if doesn't exist.
     */
    public String getLastModified() {
        if( this.headers.containsKey("Last-Modified") ) {
            return (headers.get("Last-Modified")).getFirst().toString();
        }
        else {
            Date now = new Date();
            DateFormat formatter = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss", Locale.ENGLISH);
            formatter.setTimeZone(TimeZone.getTimeZone("GMT"));
            formatter.setLenient(false);;
            return String.format("%1$s GMT", formatter.format(now));
        }
    }
    
    /*
     * return the last modified header value, or DateTime.Now() if doesn't exist.
     */
    public int getContentLength() {
        if( this.headers.containsKey("Content-Length") )
            return Integer.parseInt(
                    (headers.get("Content-Length")).getFirst().toString());
        else
            return 0;
    }
    
    /*
     * Writes 403-Forbidden reponse to the specified output stream
     */
    public static void writeForbidden(OutputStream out) throws IOException {
        synchronized( out ) {
            ServerOutputStream writer = new ServerOutputStream(out);
            writer.writeBytes(FORBIDDEN);
        }
    }
        
    public void write(OutputStream out) throws IOException
    {
        synchronized( out ) {
            ServerOutputStream dos = new ServerOutputStream(out);
            // Generate Headers
            StringBuffer headers = new StringBuffer();
            headers.append(this.statusLine);
            headers.append("\r\n");
            // Write headers
            Iterator<String> keys = this.getHeaders().keySet().iterator();
            while( keys.hasNext() )
            {
                String key = (String)keys.next();
                List<String> value = this.getHeaders().get(key);
                headers.append(
                    MessageFormat.format("{0}: ", 
                        new Object[] { key }));

                Iterator<String> values = value.iterator();
                while( values.hasNext() )
                {
                    headers.append(
                        MessageFormat.format("{0}", 
                            new Object[] { values.next() }));
                    if( values.hasNext() )
                         headers.append(", ");
                }
                // Add headers CRLF
                headers.append("\r\n");
            }
            // Another CRLF to indicate that we are done with the headers.
            headers.append("\r\n");
            // Output the headers
            dos.writeBytes(headers.toString());
            dos.flush();
            // We are done with the headers, start forwarding the request body, char by char.
            this.responseBody.writeTo(dos);
            dos.flush();
        }
    }
}
